﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using ChessKit.Properties;

namespace ChessKit.Logic
{
	public static class San
	{
		/// <summary>
		/// Gets the begin of the SAN (standard algebraic notation) for a move - 
		/// without promotion or check/checkmate representation - 
		/// before the move is made without checking the status of the 
		/// game or if it's a valid move.
		/// </summary>
		/// <param name="board">The game</param>
		/// <param name="move">The move</param>
		/// <returns></returns>
		public static string GetSan([NotNull] this Board board, MadeMove move)
		{
			if (board == null) throw new ArgumentNullException("board");

			var sb = new StringBuilder(6);

			if ((move.Hints & (MoveHints.WhiteKingsideCastling | MoveHints.BlackKingsideCastling)) != 0)
			{
				sb.Append("O-O");
			}
			else if ((move.Hints & (MoveHints.WhiteQueensideCastling | MoveHints.BlackQueensideCastling)) != 0)
			{
				sb.Append("O-O-O");
			}
			else
			{
				if ((move.Hints & MoveHints.Pawn) != 0)
				{
					if ((move.Hints & MoveHints.Capture) != 0)
						sb.Append(move.From.File);
				}
				else
				{
					var pieceType = board[move.From].GetPieceType();
					var piece = pieceType.ToPiece(PieceColor.White);
					var symbol = piece.ToEnglishSymbol();
					sb.Append(symbol);

					// TODO: move should have Piece prop?
					var disambiguationList = new List<Square>(
					  from m in board.LegalMoves
					  where m.From != move.From
						 && m.To == move.To
						 && board[move.From] == board[m.From]
					  select m.From);

					if (disambiguationList.Count > 0)
					{
						var uniqueFile = true;
						// ReSharper disable LoopCanBeConvertedToQuery
						// ReSharper disable ForCanBeConvertedToForeach
						for (var index = 0; index < disambiguationList.Count; index++)
						{
							var m = disambiguationList[index];
							if (move.From.File == m.File)
							{
								uniqueFile = false;
								break;
							}
						}
						if (uniqueFile)
						{
							sb.Append(move.From.File);
						}
						else
						{
							var uniqueRank = true;
							for (var i = 0; i < disambiguationList.Count; i++)
							{
								var m = disambiguationList[i];
								if (move.From.Rank == m.Rank)
								{
									uniqueRank = false;
									break;
								}
							}
							// ReSharper restore ForCanBeConvertedToForeach
							// ReSharper restore LoopCanBeConvertedToQuery
							if (uniqueRank)
							{
								sb.Append(move.From.Rank.ToString(CultureInfo.InvariantCulture));
							}
							else
							{
								sb.Append(move.From.ToString());
							}
						}
					}
				}

				// if there is a capture, add capture notation
				if ((move.Hints & MoveHints.Capture) == MoveHints.Capture)
				{
					sb.Append('x');
				}

				// add destination square
				sb.Append(move.To.ToString());
			}

			if ((move.Hints & MoveHints.Promotion) != 0)
				sb.Append('=').Append(move.Promotion.ToEnglishSymbol());

			switch (move.BoardAfter.ToBoard().State)
			{
				case GameState.Check:
					sb.Append('+');
					break;
				case GameState.Mate:
					sb.Append('#');
					break;
			}
			return sb.ToString();
		}

		/// <summary>
		/// Gets a move from its SAN (standard algebraic notation).
		/// Throws ArgumentException if it's not a valid move.
		/// </summary>
		/// <param name="board">The game</param>
		/// <param name="san">The SAN string</param>
		/// <param name="result"></param>
		/// <returns></returns>
		public static bool TryParseMoveFromSan([NotNull] this Board board, [NotNull] string san, out MadeMove result)
		{
			if (board == null) throw new ArgumentNullException("board");
			if (san == null) throw new ArgumentNullException("san");

			if (san == "O-O")
				return board.TryMakeMove(Move.Parse(
				  board.SideOnMove == PieceColor.White ? "e1-g1" : "e8-g8"), out result);
			if (san == "O-O-O")
				return board.TryMakeMove(Move.Parse(
				  board.SideOnMove == PieceColor.White ? "e1-c1" : "e8-c8"), out result);

			var index = san.Length - 1;
			// remove chess and checkmate representation (if any)
			if (index > -1 && (san[index] == '+' || san[index] == '#'))
				index--;
			if (index < 1)
			{
				result = new MadeMove();
				return false;
			}

			// get the promotion (if any)
			var prom = PieceType.Queen;
			if (san[index - 1] == '=')
			{
				prom = san[index].EnglishSymbolToPiece().GetPieceType();
				index -= 2;
			}

			if (index < 1)
			{
				result = new MadeMove();
				return false;
			}
			var to = Square.Parse(san.Substring(index - 1, 2));
			index -= 2;

			// remove capture char (if any)
			if (index > -1 && san[index] == 'x') index--;

			// get the rank of the starting square (if any)
			int? rank = null;
			if (index > -1 && san[index] >= '1' && san[index] <= '8')
			{
				rank = san[index] - '1';
				index--;
			}

			// get the file of the starting square (if any)
			int? file = null;
			if (index > -1 && san[index] >= 'a' && san[index] <= 'h')
			{
				file = san[index] - 'a';
				index--;
			}

			// get piece Type char (if any)
			var pieceChar = PieceType.Pawn;
			if (index > -1)
			{
				pieceChar = san[index].EnglishSymbolToPiece().GetPieceType();
				index--;
			}
			if (index != -1) throw new FormatException("Illegal characters");
			return GetMove(board, to, file, rank, pieceChar, prom, out result);
		}

		private static bool GetMove(Board board, Square to, int? file, int? rank, PieceType pieceChar, PieceType prom, out MadeMove result)
		{
			var move = default(HintedMove?);
			foreach (var m in board.LegalMoves)
				if (m.To == to)
					if (file == null || file == m.From.IntFile)
						if (rank == null || rank == m.From.IntRank)
							if (board[m.From].GetPieceType() == pieceChar)
							{
								if (move != null)
								{
									result = new MadeMove();
									return false;
								}
								move = m;
							}
			if (move == null)
			{
				result = new MadeMove();
				return false;
			}
			result = new MadeMove(board, (HintedMove)move, prom);
			return true;
		}
	}
}
